# 公开币种流动性深度做市机器人原理
# 从第三方交易所抓取价格，顺序包括 bitstamp，币安，火币, GDAX, bitfinex
# 如果已经下单的卖单的ask < best_ask，那么取消这个ask卖单
# 如果已经下单的买单的bid > best_bid ，那么取消这个bid买单
# 第三方价格参照 币安，火币，p站，bitstamp
# 机器人如果完成所有挂单跑一遍需要30秒以上，之后需要考虑机器人性能问题


require "active_support/time"
require "active_support"
require "peatio_client"
require "ap"
require "bigdecimal"
require "bigdecimal/util"


class Bot
  attr_accessor :key, :secret, :url, :base, :quote, :tradeOrderVolumeLimitUSDT
  def initialize(base, quote, key, secret, url, tradeOrderVolumeLimitUSDT = 5)
    @base = base.downcase
    @quote = quote.downcase
    @url = url
    @key = key
    @secret = secret
    @tradeOrderVolumeLimitUSDT = tradeOrderVolumeLimitUSDT
  end

  def run
    # #每次默认深度总量不超过 1000美元，需要讨论，这个数字标志最大流动性
    totalVolumeLimitUSDT = 1000
    client = PeatioAPI::Client.new access_key: key, secret_key: secret, endpoint: url
    maxOrderNum = 30.0

    totalVolumeLimitUSDT = totalVolumeLimitUSDT
    volumeLimitUpdateInteval = 3600 #每一小时更新一次Volume计算价格，3600秒
    default_btc_price = 10000
    default_eth_price = 1000

    priceValidPercent = 0.05 #当前价格和上一价格价差不超过5%视为有效价格

    # @tradeOrderVolumeLimitUSDT = 5 #每次刷单不超过五美元

    minOrderVolumeLimit = 0.0001
    priceStepLimit = 0.001
    if base == "doge" and quote == "btc"
      priceStepLimit = 0.01
    end

    priceMaxGap = priceStepLimit * maxOrderNum
    askPriceOffset = 0
    bidPriceOffset = 0
    maxTradeInteval = 30 #最大刷单间隔时间一分钟，60秒

    bitrabit_uri = URI.parse("#{url}/api/v2/depth?market=#{base}_#{quote}")

    ethusdt_uri =  URI.parse("https://www.binance.com/api/v1/depth?symbol=ETHUSDT")
    btcusdt_uri =  URI.parse("https://www.binance.com/api/v1/depth?symbol=BTCUSDT")

    market_urls = {
      "binance" => URI.parse("https://www.binance.com/api/v1/depth?symbol=#{base.upcase}#{quote.upcase}"),
      "huobi" => URI.parse("https://api.huobi.pro/market/depth?symbol=#{base}#{quote}&type=step0"),
      "gateio" => URI.parse("http://data.gate.io/api2/1/orderBook/#{base}_#{quote}"),
      "bitstamp" => URI.parse("https://www.bitstamp.net/api/v2/order_book/#{base}#{quote}/"),
      "poloniex" => URI.parse("https://poloniex.com/public?command=returnOrderBook&currencyPair=#{quote.upcase}_#{base.upcase}&depth=10"),
    }

    def market_order_price(uri)
      puts uri
      begin
        response = JSON.parse(Net::HTTP.get_response(uri).body)
      rescue Exception => e
        response = {}
      end
      wrapper = response
      if wrapper["bids"] and wrapper["asks"]
        bid_price = wrapper["bids"].collect {|e| e[0].to_f}.max
        ask_price = wrapper["asks"].collect {|e| e[0].to_f}.min
        bid_volume = wrapper["bids"].collect {|e| e[1].to_f}.max
        ask_volume = wrapper["asks"].collect {|e| e[1].to_f}.min
        return [ask_price, bid_price, ask_volume, bid_volume]
      else
        return [nil, nil, nil, nil]
      end
    end

    # 火币的深度返回格式
    def tick_wrap_market_order_price(uri)
      begin
        response = JSON.parse(Net::HTTP.get_response(uri).body)
      rescue Exception => e
        response = {}
      end
      wrapper = response["tick"]
      if wrapper and wrapper["bids"] and wrapper["asks"]
        bid_price = wrapper["bids"].collect {|e| e[0].to_f}.max
        ask_price = wrapper["asks"].collect {|e| e[0].to_f}.min
        bid_volume = wrapper["bids"].collect {|e| e[1].to_f}.max
        ask_volume = wrapper["asks"].collect {|e| e[1].to_f}.min
        return [ask_price, bid_price, ask_volume, bid_volume]
      else
        return [nil, nil, nil, nil]
      end
    end

    def generate_depth(priceMaxGap, best_ask_price, best_bid_price,minOrderVolumeLimit, orderVolumeLimit, priceStepLimit, maxOrderNum, client, market)

      orders = client.get "/api/v2/orders", market: market, state: "wait"
      return if orders.class != Array
      buy_orders = orders.select{|e| e["side"]=="buy"}.sort_by{|x| x['price'].to_f }
      sell_orders = orders.select{|e| e["side"]=="sell"}.sort_by{|x| x['price'].to_f }


      # 买单中价格小的保留maxOrderNum个订单，前maxOrderNum个
      puts "buy_orders.count #{buy_orders.count}"
      if buy_orders.count > maxOrderNum
        buy_orders_to_delete = Array(buy_orders[maxOrderNum..-1])
        buy_orders = Array(buy_orders[0...maxOrderNum])
        # byebug
        if buy_orders_to_delete
          buy_orders_to_delete.each do |order|
            puts "deleting buy order by number #{order["id"]}"
            client.post( "/api/v2/order/delete", market: market, id: order["id"] )
          end
        end
      end
      # byebug

      puts "sell_orders.count #{sell_orders.count}"

      # 卖单中价格大的保留maxOrderNum个订单，后maxOrderNum个
      if sell_orders.count > maxOrderNum
        sell_orders_to_delete = Array(sell_orders[0...sell_orders.count - maxOrderNum])
        sell_orders = Array(sell_orders[sell_orders.count - maxOrderNum...-1])
        puts "sell_orders_to_delete.count #{sell_orders_to_delete.count}"
        if sell_orders_to_delete
          sell_orders_to_delete.each do |order|
            puts "deleting sell order by number #{order["id"]}"
            client.post( "/api/v2/order/delete", market: market, id: order["id"] )
          end
        end
      end

      # byebug
      # byebug

      # 买单中价格 <= best_bid_price - priceMaxGap 的删除订单
      bid_price_limit = best_bid_price*(1 - priceMaxGap)
      puts "bid_price_limit #{bid_price_limit}"

      buy_orders_to_delete = Array(buy_orders.select{|e| e['price'].to_f <= bid_price_limit})
      buy_orders = Array(buy_orders.select{|e| e['price'].to_f > bid_price_limit})
      if buy_orders_to_delete
        buy_orders_to_delete.each do |order|
          puts "deleting buy order by price #{order["id"]}"
          client.post( "/api/v2/order/delete", market: market, id: order["id"] )
        end
      end


      # 卖单中价格 >= best_ask_price + priceMaxGap 的删除订单
      ask_price_limit = best_ask_price*(1 + priceMaxGap)
      puts "ask_price_limit #{ask_price_limit}"
      sell_orders_to_delete = Array(sell_orders.select{|e| e['price'].to_f >= ask_price_limit})
      sell_orders = Array(sell_orders.select{|e| e['price'].to_f < ask_price_limit})

      if sell_orders_to_delete
        sell_orders_to_delete.each do |order|
          puts "deleting sell order by price #{order["id"]}"
          client.post( "/api/v2/order/delete", market: market, id: order["id"] )
        end
      end

      buy_price = best_bid_price
      buy_volume = rand(minOrderVolumeLimit..orderVolumeLimit)
      client.post( "/api/v2/orders", market: market, side: "buy", volume: buy_volume, price: buy_price)
      (0...maxOrderNum - buy_orders.count).each do |e|
        puts "buy: #{e} at price #{buy_price}"
        buy_volume = rand(minOrderVolumeLimit..orderVolumeLimit)
        n = rand(1..maxOrderNum)
        rate = rand(priceStepLimit/2..priceStepLimit*1.5) * n
        buy_price = best_bid_price * (1 - rate)
        client.post( "/api/v2/orders", market: market, side: "buy", volume: buy_volume, price: buy_price)
      end

      sell_price = best_ask_price
      sell_volume = rand(minOrderVolumeLimit..orderVolumeLimit)
      client.post( "/api/v2/orders", market: market, side: "sell", volume: sell_volume, price: sell_price)
      (0...maxOrderNum - sell_orders.count).each do |e|
        puts "sell: #{e} at price #{sell_price}"
        sell_volume = rand(minOrderVolumeLimit..orderVolumeLimit)
        n = rand(1..maxOrderNum)
        rate = rand(priceStepLimit/2..priceStepLimit*1.5) * n
        sell_price = best_ask_price * (1 + rate)
        client.post( "/api/v2/orders", market: market, side: "sell", volume: sell_volume, price: sell_price)
      end
    end

    def clear_order(client, market)
      orders = client.get "/api/v2/orders", market: market, state: "wait"
      puts "clear all the orders"
      orders.each do |order|
        client.post( "/api/v2/order/delete", market: market, id: order["id"] )
      end
    end

    def mean(array)
      array.inject(0) { |sum, x| sum += x } / array.size.to_f
    end

    def find_outlier(market_prices)
      market_prices.each do |key, value|

      end
    end

    def remove_outlier(market_prices)

    end

    def get_best_price(market_urls, last_ask_price, priceValidPercent, last_best_market_name)
      best_ask_price = nil
      best_bid_price = nil
      market_prices = {}
      best_market_name = nil
      market_urls.each do |market_name, market_url|
        case market_name
        when "huobi"
          ask_price, bid_price, ask_volume, bid_volume = tick_wrap_market_order_price(market_url)
          puts "#{market_name} price: ask #{ask_price}, bid #{bid_price}"
        when "binance","bitstamp","poloniex","gateio"
          ask_price, bid_price, ask_volume, bid_volume = market_order_price(market_url)
          puts "#{market_name} price: ask #{ask_price}, bid #{bid_price}"
        end
        if ask_price
          market_prices[market_name] = [ask_price, bid_price, ask_volume, bid_volume]
        end
      end
      if market_prices.keys.include? last_best_market_name
        best_market_name = last_best_market_name
      elsif not market_prices.empty?
        best_market_name = market_prices.keys.first
      end
      asks = market_prices.values.map{|e| e[0]}
      bids = market_prices.values.map{|e| e[1]}
      spreads = market_prices.values.select{|e| e[0]}.map{|e| (e[0] - e[1])/e[0]}


      puts "available market price count: #{market_prices.count}"
      puts "有价格的数据源数量为: #{market_prices.count}"
      if market_prices.count >= 3
        # 查找outlier, 如果历史价格不是outlier，选择历史价格
        # 如果历史价格是outlier，选择inlier中点差最小的一个价格做新价格
        # outlier的定义
        # 去掉历史价格的平均点差如果和
        # 如果历史价格与其他价格点差在一个数量级，使用历史价格
        # 如果点差不在一个数量级，使用点差小的价格
        # 如果如果历史价格与其他价格，价格偏离在 priceValidPercent 以内，使用历史价格
        # 如果价差较大

        # if (asks[0] - asks[1]).abs / asks[0] > priceValidPercent
        #   # 如果有历史价格，选用离历史价格近的那个价格
        #   if last_ask_price
        #     if (asks[0] - last_ask_price).abs <= (asks[1] - last_ask_price).abs
        #       best_ask_price = asks[0]
        #       best_bid_price = bids[0]
        #     else
        #       best_ask_price = asks[1]
        #       best_bid_price = bids[1]
        #     end
        #   # 如果没有历史价格，选用点差小的那个价格
        #   else
        #     if spreads[0] <= spreads[1]
        #       best_ask_price = asks[0]
        #       best_bid_price = bids[0]
        #     else
        #       best_ask_price = asks[1]
        #       best_bid_price = bids[1]
        #     end
        #   end
        # else
        #   # 直接选价差比较小的价格,
        #   if spreads[0] <= spreads[1]
        #     best_ask_price = asks[0]
        #     best_bid_price = bids[0]
        #   else
        #     best_ask_price = asks[1]
        #     best_bid_price = bids[1]
        #   end
        # end
      elsif market_prices.count == 2
        # 如果价差在一个数据级，用历史数据源
        if (asks[0] - asks[1]).abs / asks[0] < priceValidPercent
          best_market_name = last_best_market_name
        # 如果价差不在一个数量级
        else
          # 如果有历史价格，选用离历史价格近的那个价格
          if last_ask_price
            if (asks[0] - last_ask_price).abs <= (asks[1] - last_ask_price).abs
              best_market_name = market_prices.keys.first
            else
              best_market_name = market_prices.keys.last
            end
          # 如果没有历史价格，选用点差小的那个价格
          else
            if spreads[0] <= spreads[1]
              best_market_name = market_prices.keys.first
            else
              best_market_name = market_prices.keys.last
            end
          end
        end
      elsif market_prices.count == 1
        best_market_name = market_prices.keys.first
      else
        best_market_name = nil
      end

      return market_prices, best_market_name
    end
    usdt_trans_rate_time_last = 2.days.ago
    trade_time_last = 2.days.ago
    usdt_trans_rate = nil
    last_ask_price = nil
    last_best_market_name = nil
    loop do
      begin
        if Time.now - usdt_trans_rate_time_last > volumeLimitUpdateInteval
          usdt_trans_rate_time_last = Time.now
          if quote == 'btc'
            usdt_trans_rate, bid, ask_v, bid_v = market_order_price(btcusdt_uri)
            if usdt_trans_rate.nil?
              usdt_trans_rate = default_btc_price
            end
          elsif quote == 'eth'
            usdt_trans_rate, bid, ask_v, bid_v = market_order_price(ethusdt_uri)
            if usdt_trans_rate.nil?
              usdt_trans_rate = default_eth_price
            end
          elsif quote == 'usdt'
            usdt_trans_rate = 1
          end
          puts "get usdt_trans_rate #{usdt_trans_rate}"
          puts "以usdt为基准 当前基础币#{quote} 转换率为： #{usdt_trans_rate}"
        end
        best_ask_price = nil
        best_bid_price = nil
        market_prices, best_market_name = get_best_price(market_urls, last_ask_price, priceValidPercent, last_best_market_name)
        if market_prices[best_market_name]
          best_ask_price = market_prices[best_market_name][0]
          best_bid_price = market_prices[best_market_name][1]
          last_best_market_name = best_market_name
        end
        next if best_ask_price.nil?
        puts "best_ask_price :#{best_ask_price}; best_bid_price#{best_bid_price}"
        puts "spread: #{best_ask_price - best_bid_price}"
        last_ask_price = best_ask_price

        # 以usdt计价
        rate_in_usdt = best_ask_price * usdt_trans_rate
        puts "get rate_in_usdt #{rate_in_usdt}"

        # 单次允许的最大Volume，以美元计价不超过 totalVolumeLimitUSDT
        totalVolumeLimit = totalVolumeLimitUSDT / rate_in_usdt
        # 每个挂单订单不超过的 Volume
        orderVolumeLimit = totalVolumeLimit / maxOrderNum


        puts "best price: ask #{best_ask_price}, bid #{best_bid_price}"
        puts best_ask_price - best_bid_price
        avg_price = (best_ask_price + best_bid_price)/2
        puts best_ask_price - best_bid_price



        market = "#{base.upcase}/#{quote.upcase}"
        orders = client.get "/api/v2/orders", market: market, state: "wait"
        next if orders.class != Array


        # puts orders
        # base, quote = market.split(/\//, 2)
        puts "base: #{base}; quote: #{quote}"

        base_account = client.get "/api/v2/accounts", currency: base
        quote_account = client.get "/api/v2/accounts", currency: quote
        ap base_account
        ap quote_account
        ap "orders count #{orders.count}"
        ap "已下订单总数 #{orders.count}"

        # puts orders.map{|e| e["side"]}
        orders.each do |order|
          begin
            if order["price"].to_f <= best_ask_price and order["side"] == "sell"
              puts "delete low price sell: order price #{order["price"]}, best_ask_price #{best_ask_price}"
              client.post( "/api/v2/order/delete", market: market, id: order["id"] )
            end
            if order["price"].to_f >= best_bid_price and order["side"] == "buy"
              puts "delete high price buy: order price #{order["price"]}, best_bid_price #{best_bid_price}"
              client.post( "/api/v2/order/delete", market: market, id: order["id"] )
            end
          rescue TypeError

          end
        end
        bitrabit_ask_price, bitrabit_bid_price, bitrabit_ask_volume, bitrabit_bid_volume = market_order_price(bitrabit_uri)
        puts "bitrabit price: ask #{bitrabit_ask_price}, bid #{bitrabit_bid_price}"
        puts "比特兔买一卖一价: ask #{bitrabit_ask_price}, bid #{bitrabit_bid_price}"

        # clear_order(client, market)
        # 生成深度挂单
        generate_depth(priceMaxGap, best_ask_price, best_bid_price,minOrderVolumeLimit, orderVolumeLimit, priceStepLimit, maxOrderNum, client, market)


        # # 刷量

        if Time.now - trade_time_last> rand(0..maxTradeInteval)
          trade_time_last = Time.now

          # 刷单每单不超过的 Volume
          tradeOrderVolumeLimit = tradeOrderVolumeLimitUSDT / rate_in_usdt
          tick_volume = rand(minOrderVolumeLimit..tradeOrderVolumeLimit)
          max_bid_price = [bitrabit_bid_price.to_f, best_bid_price.to_f].max
          min_ask_price = [bitrabit_ask_price.to_f, best_ask_price.to_f].min

          if min_ask_price < max_bid_price
            min_ask_price, max_bid_price = max_bid_price, min_ask_price
          end
          tick_price = rand(max_bid_price..min_ask_price)
          # byebug
          puts "trade tick_price #{tick_price}"
          client.post( "/api/v2/orders", market: market, side: "sell", volume: tick_volume, price: tick_price)
          client.post( "/api/v2/orders", market: market, side: "buy", volume: tick_volume, price: tick_price)
        end
      rescue
        puts $!.message
        puts $!.backtrace.join("\n")
        sleep(30)
      ensure
        sleep(1)
      end
    end
  end
end
